package LoginServer;

import Library.*;
import Library.Login.*;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.rmi.*;
import java.security.*;
import java.sql.SQLException;
import java.util.*;

/**
 * //TODO: javadoc
 */
public class LoginServer extends RMIServer implements ILoginServer
{
    /**
     * Variabele lijst met beschikbare gameservers
     */
    protected final Map<String, String> m_GameServers;
    /**
     * Map met usernames/accounts. Accounts worden opgehaald uit de database
     * en aangevuld met nieuwe accounts tijdens de levensduur van de server.
     */
    protected final Map<String, Account> m_Accounts;
    /**
     * Lijst met ITickets die via de LoginServer zijn aangevraagd.
     */
    protected final List<ITicket> m_Tickets = Collections.synchronizedList(new ArrayList<ITicket>());
    /**
     * Database met opslag van accounts, serverlijst en Tickets (onderdeel van account)
     */
    protected final Database m_Database;

    /**
     * Gateway waarmee de loginserver communiceert naar de clients en andersom.
     */
    protected Gateway m_Gateway = new Gateway();
    
    /**
     * intitialisatie van lijsten en maps.Vult list accounts uit de
     * database, haalt GameServers op voor m_gameservers.
     * @throws RemoteException
     * @throws SQLException
     * @throws IOException
     */
    public LoginServer() throws RemoteException,
            SQLException, IOException
    {
        super(Main.getSettings().getPort());
        

        m_Database = new Database(Main.getSettings().getConnectionString(),
                                  Main.getSettings().getUsername(), 
                                  Main.getSettings().getPassword());
     
        m_GameServers = Collections.synchronizedMap(m_Database.getServers());
        m_Accounts = Collections.synchronizedMap(m_Database.getAccounts());
    }

    /**
     * Logt een Client uit
     * @param ticket ticket van een client
     * @throws RemoteException
     * @throws Exception
     */
    public void logout(ITicket ticket) throws RemoteException, Exception
    {
        Exception ex = null;
        synchronized (m_Tickets)
        {
            try
            {
                for (Iterator<ITicket> iter = m_Tickets.iterator(); iter.hasNext();) {
                    ITicket checkTicket = iter.next();
                    if (checkTicket.getUsername().equalsIgnoreCase(ticket.getUsername()))
                    {
                        iter.remove();
                         m_Database.removeTicket(ticket);
                    }
                }
            }
            catch (Exception e)
            {
                ex = e;
            }
        }
        if (ex != null)
            throw ex;
    }
    
    /**
     * Returns a map of gameservers by servername which cannot be modified.
     * @return gameserver
     */
    public Map<String, String> getGameServers()
    {
        Map<String, String> servers = new HashMap<String, String>();
        this.refreshServers();
        synchronized (m_GameServers)
        {
            servers.putAll(m_GameServers);
        }
        return Collections.unmodifiableMap(servers);
    }
    
    /**
     * Returns a map of accounts by accountname which cannot be modified.
     * @return accounts
     */
    public Map<String, Account> getAccounts()
    {
        
        Map<String, Account> accs = new HashMap<String, Account>();
        this.refreshAccounts();
        synchronized (m_Accounts)
        {
            accs.putAll(m_Accounts);
        }
        return Collections.unmodifiableMap(accs);
    }

    /**
     * Geeft een lijst met tickets terug
     * @return lijst met tickets
     */
   public List<ITicket> getTickets()
   {
       return m_Tickets;
   }

    /**
     * Start de login server
     * @return succesvol
     */
    @Override
    public boolean startServer()
    {
        synchronized (m_Tickets)
        {
            m_Tickets.clear();
        }
        //TODO: Resolve Sleep
        super.startServer();
        try
        {
            sleep(1000);
        }
        catch (InterruptedException ex)
        {
            ex.printStackTrace();
        }
        if (getRunning())
        {
            try
            {
                HostObject("LoginServer", this);
                m_Gateway.SetLoginServer(this);
                HostObject("Gateway", m_Gateway);
            }
            catch (RemoteException ex)
            {
                ex.printStackTrace();
            }
            catch (AlreadyBoundException ex)
            {
                ex.printStackTrace();
            }
            return true;
        }
        return false;
    }

    /**
     * Met username/password wordt een account aangemaakt. Als deze bestaat in
     * database,wordt ticket gecreeerd en teruggegeven.
     * @param username
     * @param password
     * @return ITicket or null
     * @throws RemoteException
     * @throws UnsupportedEncodingException
     * @throws NoSuchAlgorithmException
     * @throws Exception
     */
    public ActionResult<ITicket> login(String username, int password) throws RemoteException,
            NoSuchAlgorithmException, UnsupportedEncodingException, Exception
    {
        ITicket ticket = null;
        //byte[] pwdHash = password.getBytes("UTF-8");
        //MessageDigest md = MessageDigest.getInstance("SHA-1");
        //pwdHash = md.digest(pwdHash);

	int result = ErrorCodes.ERROR_NOERROR;
        Exception ex = null;
        synchronized (m_Database)
        {
            try
            {
                // kijk of username in m_Accounts zit
                if (m_Accounts.containsKey(username.toUpperCase()))
                {
                    // vraag account op in m_Accounts via username
                    Account a = m_Accounts.get(username.toUpperCase());
                    
                    // Is er al een ticket bestaat voor deze user?
		    if (password == a.getPasswordHash())
                    {
			boolean gevonden = false;
			int i = 0;
			while (i < m_Tickets.size() && !gevonden)
			{
			    ITicket kaart =  m_Tickets.get(i);
			    if (kaart.getUsername().equals(a.getUsername()))
				gevonden = true;                                    
			    i++;
			}
			if (!gevonden)
			{
			    getGameServers();
			    ticket = new Ticket(a, m_GameServers);
			    m_Tickets.add(ticket);
			    m_Database.saveTicket(ticket);
			}
			else
			    result = ErrorCodes.ERROR_ALREADY_LOGGED_IN;
		    }
		    else
			result = ErrorCodes.ERROR_INVALID_PASSWORD;
                }
		else
		    result = ErrorCodes.ERROR_ACCOUNT_NOT_FOUND;
            }
            catch (Exception e)
            {
		result = ErrorCodes.ERROR_UNKNOWN;
                ex = e;
            }
        }
        
        if (ex != null)
            throw ex;

        return new ActionResult<ITicket>(result, ticket);
    }
    
    /**
     * Ruimt alle gebruikte resources netjes op
     * @throws RemoteException
     */
    public void dispose() throws RemoteException
    {
        if (m_Database != null)
        {
            m_Database.removeAllTickets();
            m_Database.dispose();
        }
    }

    /**
     * Maakt met behulp van een username en password een account aan. Controle
     * op dubbele accounts zit in de database. Als creatie in database lukt,
     * wordt account toegevoegd aan list m_accounts en True teruggegeven.
     * @param username
     * @param password
     * @return result (zie Library.ErrorCodes)
     * @throws RemoteException
     * @throws NoSuchAlgorithmException
     * @throws UnsupportedEncodingException
     */
    public int createAccount(String username, int password)
            throws RemoteException, UnsupportedEncodingException,
            NoSuchAlgorithmException
    {
        Account account = null;
        int result = m_Database.createAccount(username, password);
        if (result == ErrorCodes.ERROR_NOERROR)
        {
            account = new Account(username, password);
            this.refreshAccounts();
            return result;
        }
        else
        {
            return result;
        }
    }

    /**
     * voegt nieuwe server toe aan de database
     * @param Servername
     * @param IP_Port
     * @return result (zie Library.ErrorCodes)
     * @throws RemoteException
     * @throws UnsupportedEncodingException
     * @throws NoSuchAlgorithmException
     */
    public int addServer(String Servername, String IP_Port)
            throws RemoteException, UnsupportedEncodingException,
            NoSuchAlgorithmException
    {
        int result = m_Database.addServer(Servername, IP_Port);
        if (result == ErrorCodes.ERROR_NOERROR)
            this.refreshServers();
            return result;
    }

    /**
     * verwijdert opgegeven server uit de database
     * @param Servername
     * @return result (zie Library.ErrorCodes)
     * @throws RemoteException
     * @throws UnsupportedEncodingException
     * @throws NoSuchAlgorithmException
     */
    public int deleteServer(String Servername)
            throws RemoteException, UnsupportedEncodingException,
            NoSuchAlgorithmException
    {
        int result = m_Database.deleteServer(Servername);

        if (result == ErrorCodes.ERROR_NOERROR)
            this.refreshAccounts();
            return result;
    }

    /**
     * verwijdert opgegeven account uit de database
     * @param username
     * @return result (zie Library.ErrorCodes)
     * @throws RemoteException
     * @throws UnsupportedEncodingException
     * @throws NoSuchAlgorithmException
     */
    public int deleteAccount(String username)
            throws RemoteException, UnsupportedEncodingException,
            NoSuchAlgorithmException
    {
        int result = m_Database.deleteAccount(username);

        if (result == ErrorCodes.ERROR_NOERROR)
            this.refreshAccounts();
            return result;
    }
    
    /**
     * Cleared na aanroep map Accounts, haalt nieuwe data op uit de database en
     * zet deze in map Accounts
     */
    public void refreshAccounts()
    {
        m_Accounts.clear();
        if (m_Accounts.isEmpty())
        {
            Map map = m_Database.getAccounts();
            m_Accounts.putAll(map);
        }
    }

    /**
     * Cleared na aanroep map Servers, haalt nieuwe data op uit de database en
     * zet deze in map Accounts
     */
    public void refreshServers()
    {
        m_GameServers.clear();
        if (m_GameServers.isEmpty())
        {
            Map map = m_Database.getServers();
            m_GameServers.putAll(map);
        }
    }


}
